package old.galaxyGeneration;

import java.util.Arrays;
import java.util.Comparator;

import objects.GameSettings;
import objects.Universe;
import objects.physicalEntities.Star;

import inputOutput.PlayerSettings;
import inputOutput.XMLoader;

public class MultiClusterGalaxy extends Galaxy 
{
	//Player settings
	//-----------------------------------------------
	private int numClusters;  //for MultiCluster option
	//-----------------------------------------------
	
	//XML
	//-----------------------------------------------
	protected double multiClusterStdDevFactor1;//associated with the variance in cluster std. deviations
	protected double multiClusterStdDevFactor2;//associated with mean value of cluster std. deviations
	protected double minClusterDistFactor;//associated with minimum distance between clusters
	//-----------------------------------------------
	
	public MultiClusterGalaxy(){}
	
	public MultiClusterGalaxy(GameSettings gameSettings, Universe universe) 
	{		
		super(gameSettings, universe);		
		numClusters = gameSettings.getNumClusters();		
		multiClusterStdDevFactor1 = gameSettings.getMultiClusterStdDevFactor1();
		multiClusterStdDevFactor2 = gameSettings.getMultiClusterStdDevFactor2();
		//XMLoader.loadData(this, "Settings\\MultiClusterGalaxyMap.xml", "Settings\\MultiClusterGalaxy.xml");
		
		this.galaxyGenerator();
	}	
	
	protected void galaxyGenerator()
	{
		System.out.println("Just inside MultiCluster");

		if (numStars < 100)
		{System.out.println("Need at least 100 stars!"); return;}		
		
		double clusterBorderZone1;  //border region where no clusters may be placed (left)
		double clusterBorderZone2;  //border region where no clusters may be placed (right)
		double starBorderZone1 = 3;
		double starBorderZone2 = uniLength-4;
		double minClusterDist;  //minimum distance between cluster centers--avoids too much overlap
		//double minClusterDev;  //minimum spread of stars in a given cluster--avoids compacting clusters
		double[] starAlloc;  //holds proportion of total stars to be placed in each cluster
		double allocSum;  //sum of starAlloc
		int tempSum;  
		boolean distFlag;  //indicates if new cluster position is too close to existing cluster position(s)
		double clusterDeviation;  //current cluster std. deviation
		double distance;  //distance between any two points in starGrid
		int[] placedPerCluster;  //number of stars placed in each cluster
		
		//Reset array (just in case)
		for (int i = 0; i < uniLength; i++)
		{
			for (int j = 0; j < uniLength; j++)
			{
				starGrid[i][j] = 0;
			}
		}

		//Establish border region where no clusters may be placed (restricted to workingLength)
		clusterBorderZone1 = uniLength*1/4;
		clusterBorderZone2 = uniLength*3/4;

		//Minimum allowable distance between clusters.  Gets smaller with more clusters or fewer stars.
		minClusterDist = (workingLength-minStarsInit/minClusterDistFactor)/numClusters;
		//minClusterDev = workingLength/16;  //smallest std. deviation of stars in a cluster

		starAlloc = new double[numClusters];
		allocSum = 0;
		//Determine proportion of stars in each cluster
		for (int i = 0; i < numClusters; i++)
		{
			starAlloc[i] = randUni.nextDouble()*0.8+0.1;
			allocSum += starAlloc[i];				
		}

		Cluster[] clusters = new Cluster[numClusters];
		tempSum = 0;
		//Normalize
		for (int i = 0; i < numClusters; i++)
		{
			clusters[i] = new Cluster((int)(numStars*starAlloc[i]/allocSum));
			tempSum += clusters[i].getNumStars();
		}
		
		//May have lost a star or two in rounding to (int) above, so correct
		if (tempSum > numStars)
		{clusters[0].setNumStars(clusters[0].getNumStars()-(tempSum - numStars));}
		else if (tempSum < numStars)
		{clusters[0].setNumStars(clusters[0].getNumStars()+(numStars - tempSum));}
		
		//Sort to put largest cluster first.  Largest cluster gets first crack at placement on map
		Arrays.sort(clusters, new ClusterComparator());			
		
		//Place cluster centers
		for (centersPlaced = 0; centersPlaced < numClusters; centersPlaced++)
		{
			distFlag = true;

			//Determine standard deviation of cluster.
			//First term introduces variance into the deviations
			//Second term is the mean of the deviations, set to a particular value.
			clusterDeviation = (randUni.nextDouble()*0.6+0.2)*clusters[centersPlaced].getNumStars()/multiClusterStdDevFactor1 + clusters[centersPlaced].getNumStars()/multiClusterStdDevFactor2;

			//Place the largest cluster within workingLength/6 of center
			if (centersPlaced == 0)
			{
				tempX = uniLength/4+workingLength*(randUni.nextDouble()*0.333+0.333);
				tempY = uniLength/4+workingLength*(randUni.nextDouble()*0.333+0.333);					
			}
			//otherwise let them fall where they may within 1/10 to 9/10 of workingLength
			else
			{
				tempX = uniLength/4+workingLength*(randUni.nextDouble()*0.8+0.1);
				tempY = uniLength/4+workingLength*(randUni.nextDouble()*0.8+0.1);					
			}
			
			//Ensure that the current center is far enough from the others
			for (int i = 0; i < centersPlaced; i++)
			{
				distance = Math.sqrt((tempX-clusters[i].getX())*(tempX-clusters[i].getX()) + (tempY-clusters[i].getY())*(tempY-clusters[i].getY()));
				if (distance < minClusterDist)
				{distFlag = false;}					
			}

			//If far enough away from other clusters
			if (distFlag == true)
			{	
				//if not in the border areas
				if ((tempX > clusterBorderZone1) & (tempY > clusterBorderZone1) & (tempX < clusterBorderZone2) & (tempY < clusterBorderZone2))// & (clusterDeviation > minClusterDev))
				{
					clusters[centersPlaced].setX((int)tempX);
					clusters[centersPlaced].setY((int)tempY);
					clusters[centersPlaced].setDev(clusterDeviation);						
				}				
				else
				{centersPlaced -= 1;}			
			}		
			else
			{centersPlaced -= 1;}
		}				
		
		//Place stars at each cluster (do so evenly, looping through clusters)
		placedPerCluster = new int[numClusters];
		starsPlaced = 0;
		
		while (starsPlaced < numStars)
		{
			for (int i = 0; i < numClusters; i++)
			{
				if (placedPerCluster[i] < clusters[i].getNumStars())
				{
					tempX = randG1.nextGaussian()*clusters[i].getDev()+clusters[i].getX();
					tempY = randG2.nextGaussian()*clusters[i].getDev()+clusters[i].getY();

					x = (int)tempX;
					y = (int)tempY;
					
					//Ensure stars are not placed right next to the border of the grid
					if ((x > starBorderZone1) & (y > starBorderZone1) & (x < starBorderZone2) & (y < starBorderZone2))
					{
						placedPerCluster[i]++;
						starsPlaced++;
						//check starGrid values at location
						if (starGrid[x][y] == 0)
						{
							universe.AddStar(new StarModel(x,y));
							updateGrid(x,y);					
						}

						else if ((starGrid[x][y] == 1) & (randUni.nextDouble() < ringTwo))
						{
							universe.AddStar(new StarModel(x,y));
							updateGrid(x,y);						
						}

						else if ((starGrid[x][y] == 2) & (randUni.nextDouble() < ringOne))
						{
							universe.AddStar(new StarModel(x,y));
							updateGrid(x,y);
						}
						else
						{
							starsPlaced--;
							placedPerCluster[i]--;
						}						
					}						
				}
			}
		}
		universe.PrintStars();		
	}
	
	class ClusterComparator implements Comparator<Object> {

		public int compare(Object clust1, Object clust2) {
			int clust1Stars = ((Cluster)clust1).numStars;
			int clust2Stars = ((Cluster)clust2).numStars;
			
			if (clust1Stars > clust2Stars)
				return -1;
			else if (clust1Stars < clust2Stars)
				return 1;
			else
				return 0;			
		}
	}

	public double getMultiClusterStdDevFactor1() {
		return multiClusterStdDevFactor1;
	}


	public void setMultiClusterStdDevFactor1(double multiClusterStdDevFactor1) {
		this.multiClusterStdDevFactor1 = multiClusterStdDevFactor1;
	}


	public double getMultiClusterStdDevFactor2() {
		return multiClusterStdDevFactor2;
	}


	public void setMultiClusterStdDevFactor2(double multiClusterStdDevFactor2) {
		this.multiClusterStdDevFactor2 = multiClusterStdDevFactor2;
	}


	public double getMinClusterDistFactor() {
		return minClusterDistFactor;
	}


	public void setMinClusterDistFactor(double minClusterDistFactor) {
		this.minClusterDistFactor = minClusterDistFactor;
	}
	
	

}
